//! 同步的相关逻辑
//!
//! 主要包括两方面，九宫格和玩家位置同步测率。游戏本体是单机游戏，但支持玩家创建房间一起玩耍。所以也是一个多人游戏。
//!
//! 玩家位置同步主要是玩家的状态同步。
//! 如果是单人主机，那么不存在网络延迟问题。
//! 如果是多人房间，那么非主机玩家与主机之间就会存在时差和网络延迟问题
//! 大概的步骤如下
//! 1. 对时。
//!
//!
//!
//! 对时和计算出正确的答案需要在外面包一层。这里不做处理才对？
//!
//!

use crate::game::GameStartMsg;
use crate::location::unit::{
    SimpleUnitChangeStateMsg, SimpleUnitEnterGridMsg, SimpleUnitLeaveGridMsg, Unit, UnitState,
};
use crate::map::generate_terrain;
use crate::util::wrap_iter::WrapIterExt;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::collections::HashMap;
use std::time::SystemTime;
use the_world_game_core::{
    log::info,
    result::{anyhow, Result},
    service_send_msg, Handler, Worker, WorkerContextImpl,
};
use the_world_game_derive::*;

const RAND_FACTOR: u32 = 40;

/// 单位状态变化时横向格子通知半径
const GRID_NOTICE_COL_RADIUS: usize = 2;
/// 单位状态变化时纵向格子通知半径
const GRID_NOTICE_ROW_RADIUS: usize = 2;

/// aoi管理，采用九宫格实现
#[derive(Serialize, Deserialize)]
pub struct LocationManager {
    // 所有的格子
    grids: Vec<Vec<Grid>>,
    // 位置信息 用来查询单位所在的位置 方便定位
    location_info: HashMap<usize, (usize, usize)>,
}

impl LocationManager {
    pub fn new() -> Self {
        LocationManager {
            grids: vec![vec![]],
            location_info: HashMap::new(),
        }
    }

    /// 获取附近的的单位，并进行特定的处理
    fn handle_nearby_unit<F>(&self, grid_coordinate: (usize, usize), handle: F)
    where
        F: Fn(&Unit),
    {
        let (grid_x, grid_y) = grid_coordinate;
        self.grids
            .iter()
            .wrap_iter(grid_x, GRID_NOTICE_ROW_RADIUS)
            .for_each(|vec| {
                vec.iter()
                    .wrap_iter(grid_y, GRID_NOTICE_COL_RADIUS)
                    .for_each(|grid| {
                        // 对所有格子里能到观察到变化的格子进行通知
                        grid.units.iter().for_each(&handle);
                    });
            });
    }

    /// 当状态发生变化时，通知周围的格子里的单位
    /// 当状态发生变化时需要通知
    /// 当某个单位离开格子时，需要通知
    /// 当某个单位进入格子时，需要通知
    ///
    /// 根据上面的结果，单位仅仅状态变化时通知一次
    /// 但是单位在离开格子进入另一个格子时，离开通知一次，进入通知一次
    ///
    /// 在不考虑边界的情况下，视野不会超过上下左右5个格子
    /// 在这些格子里视野可以窥见的单位会被警醒
    ///
    /// 这里面还有许多情况需要考虑，例如地形陷阱，或者单位已经隐身，那么即便在某些单位视野内也不应该被发现
    ///
    /// 需要的参数 变化的单位所在格子
    fn broadcast_state_change(&self, msg: SimpleUnitChangeStateMsg) {
        self.handle_nearby_unit(msg.grid_coordinate, |u| {
            u.find_nearby_unit_state_change(&msg);
        });
    }

    /// 广播单位离开旧格子
    fn broadcast_unit_leave(&mut self, msg: SimpleUnitLeaveGridMsg) {
        self.handle_nearby_unit(msg.grid_coordinate, |u| {
            u.find_nearby_unit_leave_grid(&msg);
        });
    }

    /// 广播单位进入新格子
    fn broadcast_unit_enter(&mut self, msg: SimpleUnitEnterGridMsg) {
        self.handle_nearby_unit(msg.grid_coordinate, |u| {
            u.find_nearby_unit_enter_grid(&msg);
        });
    }

    /// 根据地址和id定位单位，返回单位的可变借用
    fn find_unit(&mut self, id: usize) -> Result<&mut Unit> {
        if let Some((x, y)) = self.location_info.get(&id) {
            let unit = &mut self.grids[*x][*y].units;
            if let Some(unit) = unit.iter_mut().find(|u| u.get_id() == id) {
                return Ok(unit);
            } else {
                return Err(anyhow!("格子内定位不到 id {}的位置信息", id));
            }
        } else {
            Err(anyhow!("查询不到id {} 的位置信息", id))
        }
    }

    /// 根据id定位单位，并从格子中移除并返回
    fn pop_unit(&mut self, id: usize) -> Result<Unit> {
        if let Some((x, y)) = self.location_info.remove(&id) {
            let unit = &mut self.grids[x][y].units;
            if let Some((index, _)) = unit.iter_mut().enumerate().find(|(_, u)| u.get_id() == id) {
                let unit = unit.remove(index);
                return Ok(unit);
            } else {
                return Err(anyhow!("格子内定位不到 id {}的位置信息", id));
            }
        } else {
            Err(anyhow!("查询不到id {} 的位置信息", id))
        }
    }

    /// 把单位插入格子
    fn push_unit(&mut self, unit: Unit) {
        let (grid_coordinate_x, grid_coordinate_y) = unit.get_grid_coordinate();
        self.location_info
            .insert(unit.get_id(), (grid_coordinate_x, grid_coordinate_y));
        let units = &mut self.grids[grid_coordinate_x][grid_coordinate_y].units;
        units.push(unit);
    }
}

/// 格子
#[derive(Serialize, Deserialize)]
struct Grid {
    // 坐标
    coordinate: (usize, usize),
    // 格子基本信息
    info: GridInfo,
    // 格子里的单位 一个格子最多20个单位
    units: SmallVec<[Unit; 20]>,
}

/// 格子基本信息
#[derive(Serialize, Deserialize)]
struct GridInfo {
    // 地形
    // 0-water
    // 1-land
    // other-mountain
    terrain: i8,
    // 状态
}

trait UnitType {}

impl Worker for LocationManager {
    type Context = WorkerContextImpl<Self>;

    fn started(&mut self, _ctx: &mut Self::Context) {
        info("AOI Worker启动");
    }

    fn stopped(&mut self, _ctx: &mut Self::Context) {
        // 序列化
    }
}

impl LocationManager {
    // 初始化格子信息
    fn init_grid_info(&mut self) {
        let length = self.grids.len();
        let width = self.grids[0].len();
        // 生成地形图
        let terrain = generate_terrain(length, width, RAND_FACTOR);
        // 赋值
        for (grid_vec, terrain_vec) in self.grids.iter_mut().zip(terrain.iter()) {
            for (grid, terrain_val) in grid_vec.iter_mut().zip(terrain_vec.iter()) {
                grid.info.terrain = *terrain_val;
            }
        }
    }
}

// 处理游戏信息
impl Handler<GameStartMsg> for LocationManager {
    fn handle(&mut self, msg: GameStartMsg) -> Result<()> {
        if msg.new_game {
            // 新游
            // 常见地图 然后为地图创建aoi管理
            self.init_grid_info();
        } else {
            // 加载存档
            let load = msg.load.unwrap();
            let data: LocationManager = bincode::deserialize(&load).unwrap();
            // 直接加载数据
            *self = data;
        }
        Ok(())
    }
}

/// 状态信息
/// 用来进行状态的同步
#[derive(Message)]
struct UnitStateChangeMsg {
    /// 哪个单位
    id: usize,
    /// 新状态
    state: UnitState,
    /// 修改时间
    time: SystemTime,
    /// 目前的位置
    coordinate: (usize, usize),
}

/// 进入新格子信息
#[derive(Message)]
struct UnitEnterGridMsg {
    unit: Unit,
}

/// 离开旧格子的信息
#[derive(Message)]
struct UnitLeaveGridMsg {
    /// 哪个单位
    id: usize,
    /// 离开后是否需要加入新格子
    enter_new_flag: bool,
    /// 新格子
    new_grid_coordinate: (usize, usize),
    /// 新坐标
    new_coordinate: (usize, usize),
    /// 修改时间
    time: SystemTime,
}

// 单位处理

/// 处理状态信息
/// 当单位开始移动时，需要发送消息更新单位状态为移动+方向
/// 当gui计算到跨格子时，需要发送消息来询问可见
/// 后端接受跨格子信息再验证玩家移动是否正常
/// 正常给予发送新的九宫格消息，并处理相关逻辑
///
/// TODO 某些异常是否需要进行特殊操作？不然全都可以用anyhow来解决
impl Handler<UnitStateChangeMsg> for LocationManager {
    fn handle(&mut self, msg: UnitStateChangeMsg) -> Result<()> {
        let unit = self.find_unit(msg.id)?;

        // 验证下目前的位置是否合法
        if unit.validate_new_coordinate(msg.coordinate) {
            let old_state = unit.get_state();
            let old_coordinate = unit.get_coordinate();
            unit.set_state(msg.state);
            unit.set_last_modify_state_time(msg.time);
            unit.set_coordinate(msg.coordinate);
            let msg = SimpleUnitChangeStateMsg {
                ty: unit.get_type(),
                old_state,
                new_state: msg.state,
                old_coordinate,
                new_coordinate: msg.coordinate,
                grid_coordinate: unit.get_grid_coordinate(),
            };
            self.broadcast_state_change(msg);
        } else {
            return Err(anyhow!("修改单位状态时，发现位置异常"));
        }

        Ok(())
    }
}

/// 进入格子
/// 某些格子，当单位进入时可能出发特殊事件
impl Handler<UnitEnterGridMsg> for LocationManager {
    fn handle(&mut self, msg: UnitEnterGridMsg) -> Result<()> {
        // 取出单位
        let unit = msg.unit;

        let ty = unit.get_type();

        let msg = SimpleUnitEnterGridMsg {
            ty,
            coordinate: unit.get_coordinate(),
            grid_coordinate: unit.get_grid_coordinate(),
        };
        self.broadcast_unit_enter(msg);
        // 插入新格子
        self.push_unit(unit);
        Ok(())
    }
}

impl Handler<UnitLeaveGridMsg> for LocationManager {
    fn handle(&mut self, msg: UnitLeaveGridMsg) -> Result<()> {
        // 如果需要进入新的格子，需要首先验证数据是否正常
        if msg.enter_new_flag {
            let unit = self.find_unit(msg.id)?;
            if !unit.validate_new_grid_coordinate(msg.new_grid_coordinate)
                || !unit.validate_new_coordinate(msg.new_coordinate)
            {
                return Err(anyhow!("想要进入的新格子异常导致离开旧格子失败"));
            }
        }
        // 取出单位
        let mut unit = self.pop_unit(msg.id)?;
        let leave_msg = SimpleUnitLeaveGridMsg {
            ty: unit.get_type(),
            grid_coordinate: unit.get_grid_coordinate(),
            coordinate: unit.get_coordinate(),
        };
        self.broadcast_unit_leave(leave_msg);
        if msg.enter_new_flag {
            // 修改单位信息
            unit.set_last_modify_state_time(msg.time);
            unit.set_grid_corrdinate(msg.new_grid_coordinate);
            unit.set_coordinate(msg.new_coordinate);
            // 发送添加信息
            service_send_msg::<LocationManager, UnitEnterGridMsg>(UnitEnterGridMsg { unit });
        }
        Ok(())
    }
}
